// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// go:build aix | | darwin | | | dragonfly | | freebsd | | linux | | netbsd | | openbsd | | solaris 

package os

import (
	"internal/syscall/unix"
	"io"
	"syscall"
)

func removeAll(path string) error {
	if path == "" {
		// 悄悄地无法保持与以前行为的兼容性
		// 。见第28830期。
		return nil
	}

	// rmdir系统调用不允许删除“.”，
	// 所以我们也不允许。
	if endsWithDot(path) {
		return &PathError{Op: "RemoveAll", Path: path, Err: syscall.EINVAL}
	}

	// 简单的例子：如果删除有效，我们就完成了。
	err := Remove(path)
	if err == nil || IsNotExist(err) {
		return nil
	}

	// RemoveAll通过从
	// 其父目录
	parentDir, base := splitPath(path)

	parent, err := Open(parentDir)
	if IsNotExist(err) {
		// 中删除路径base来递归。如果父目录不存在，则base不能存在。无声失败
		return nil
	}
	if err != nil {
		return err
	}
	defer parent.Close()

	if err := removeAllFrom(parent, base); err != nil {
		if pathErr, ok := err.(*PathError); ok {
			pathErr.Path = parentDir + string(PathSeparator) + pathErr.Path
			err = pathErr
		}
		return err
	}
	return nil
}

func removeAllFrom(parent *File, base string) error {
	parentFd := int(parent.Fd())
	// 简单的例子：如果取消链接（又称删除）有效，我们就完成了。
	err := unix.Unlinkat(parentFd, base, 0)
	if err == nil || IsNotExist(err) {
		return nil
	}

	// EISDIR意味着我们有一个目录，我们需要删除它的内容。
	// EPERM或EACCES表示我们对父目录
	// 没有写权限，但此条目可能仍然是一个目录
	// 其内容需要删除。
	// 否则返回错误。
	if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES {
		return &PathError{Op: "unlinkat", Path: base, Err: err}
	}

	// 这是我们需要递归到的目录吗？
	var statInfo syscall.Stat_t
	statErr := unix.Fstatat(parentFd, base, &statInfo, unix.AT_SYMLINK_NOFOLLOW)
	if statErr != nil {
		if IsNotExist(statErr) {
			return nil
		}
		return &PathError{Op: "fstatat", Path: base, Err: statErr}
	}
	if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR {
		// 不是目录；从unix返回错误。解除链接。
		return &PathError{Op: "unlinkat", Path: base, Err: err}
	}

	// 删除目录项。
	var recurseErr error
	for {
		const reqSize = 1024
		var respSize int

		// 打开目录以递归到
		file, err := openFdAt(parentFd, base)
		if err != nil {
			if IsNotExist(err) {
				return nil
			}
			recurseErr = &PathError{Op: "openfdat", Path: base, Err: err}
			break
		}

		for {
			numErr := 0

			names, readErr := file.Readdirnames(reqSize)
			// EOF以外的错误应阻止我们继续。
			if readErr != nil && readErr != io.EOF {
				file.Close()
				if IsNotExist(readErr) {
					return nil
				}
				return &PathError{Op: "readdirnames", Path: base, Err: readErr}
			}

			respSize = len(names)
			for _, name := range names {
				err := removeAllFrom(file, name)
				if err != nil {
					if pathErr, ok := err.(*PathError); ok {
						pathErr.Path = base + string(PathSeparator) + pathErr.Path
					}
					numErr++
					if recurseErr == nil {
						recurseErr = err
					}
				}
			}

			// 如果我们可以删除任何条目，请中断以开始新的迭代。
			// 否则，我们将放弃当前名称，获取下一个条目并尝试删除它们。
			if numErr != reqSize {
				break
			}
		}

		// 从目录中删除文件可能导致操作系统对其进行了重新洗牌。只需再次调用Readdirnames 
		// 可能会跳过一些条目。
		// 避免这种情况的唯一可靠方法是关闭并重新打开
		// 目录。见第20841期。
		file.Close()

		// 到达目录末尾时完成
		if respSize < reqSize {
			break
		}
	}

	// 删除目录本身。
	unlinkError := unix.Unlinkat(parentFd, base, unix.AT_REMOVEDIR)
	if unlinkError == nil || IsNotExist(unlinkError) {
		return nil
	}

	if recurseErr != nil {
		return recurseErr
	}
	return &PathError{Op: "unlinkat", Path: base, Err: unlinkError}
}

// openFdAt打开相对于fd中目录的路径。
// 除了这应该像openFileNolog一样。
// 这就像openFileNolog，而不是OpenFile，因为
// 我们将（尝试）删除该文件。
// 此文件的内容与测试缓存无关。
func openFdAt(dirfd int, name string) (*File, error) {
	var r int
	for {
		var e error
		r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC, 0)
		if e == nil {
			break
		}

		// 请参阅openFileNolog中的注释。
		if e == syscall.EINTR {
			continue
		}

		return nil, e
	}

	if !supportsCloseOnExec {
		syscall.CloseOnExec(r)
	}

	return newFile(uintptr(r), name, kindOpenFile), nil
}
